/*
* This file is a part of the open-eBackup project.
* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
* If a copy of the MPL was not distributed with this file, You can obtain one at
* http://mozilla.org/MPL/2.0/.
*
* Copyright (c) [2024] Huawei Technologies Co.,Ltd.
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
*/
#ifdef WIN32
#include "WinCmdExector.h"
#include <tchar.h>
#include <iostream>
#include <map>
#include <cerrno>
#include <memory>
#include <fstream>
#include "log/Log.h"
#include "common/uuid/Uuid.h"
#include "securec.h"
#include "common/Types.h"
#include "common/File.h"
#include "common/Constants.h"

namespace {
const std::string MODULE_NAME = "WinCmdExector";
const std::string SEMICOLON_STR = ";";
const std::string VERTICAL_LINE_STR = "|";
const std::string ADDRESS_STR = "&";
const std::string STR_QUOTES = "\"";
const std::string SPACE_STR = " ";
constexpr int RETRY_TIME = 3;
constexpr int SENINFOWIPETIMES = 3;
using Defer = std::shared_ptr<void>;
const std::map<std::string, int> ABNORMAL_INDICATOR = {
    {SEMICOLON_STR, 0}, {VERTICAL_LINE_STR, 0},
    {ADDRESS_STR, 0}, {Module::SIGN_IN, 0},
    {Module::SIGN_OUT, 0}, {Module::SIGN_BACKQUOTE, 0},
    {Module::SIGN_EXCLAMATION, 0},
    {Module::STR_CODE_WARP, 0}, {Module::SIGN_DOLLAR, 0}
};
const std::string VIRT_HYPERV_PARAM_PATH = Module::EnvVarManager::GetInstance()->GetAgentHomePath() +
    "\\DataBackup\\ProtectClient\\Plugins\\VirtualizationPlugin\\tmp\\";
const std::string VIRT_HYPERV_RESULT_PATH = Module::EnvVarManager::GetInstance()->GetAgentHomePath() +
    "\\DataBackup\\ProtectClient\\Plugins\\VirtualizationPlugin\\stmp\\";
const std::string CMD_TYPE_LIST_DISK = "ListDisk";
}

using namespace VirtPlugin;

namespace HyperVPlugin {
namespace Utils {

int WinCmdExector::WriteParam(Json::Value &inputValue, std::string &uniqueId)
{
    LOGGUARD("");
    uniqueId = VirtPlugin::Uuid::GenerateUuid();
    if (uniqueId.empty()) {
        ERRLOG("Generate unique id failed.");
        return FAILED;
    }

    inputValue["RequestId"] = m_param.requestId.empty() ? uniqueId : m_param.requestId;
    inputValue["TargetHost"] = GetTargetHost();
    inputValue["TargetType"] = static_cast<int32_t>(GetTargetType());
    if (WriteInfoToParamFile(GetParamFile(uniqueId), inputValue) != SUCCESS) {
        ERRLOG("Failed to write param to file.");
        return FAILED;
    }
    return SUCCESS;
}

int WinCmdExector::Execute(const Param &cmdParam, Json::Value &result)
{
    LOGGUARD("");
    m_param = cmdParam;
    std::string uniqueId;
    Json::Value tempParm = std::move(cmdParam.param);
    std::string scriptName;

    int retRes = WriteParam(tempParm, uniqueId);
    if (retRes != SUCCESS) {
        ERRLOG("Split param failed.");
        return retRes;
    }

    Defer _(nullptr, [&](...) {
        DeleteResultFile(GetParamFile(uniqueId));
        DeleteResultFile(GetResultFile(uniqueId));
    });

    scriptName = cmdParam.scriptDir
        + (cmdParam.scriptDir.empty() ? "" : Module::STR_BACKSLASH + cmdParam.scriptName);
    std::vector<std::string> execArgs {
        scriptName,
        "-Command-Type", cmdParam.cmdType,
        "-UniqId", uniqueId
    };
    int res = ExecuteCmd(execArgs);
    if (res != SUCCESS) {
        ERRLOG("Execute cmd failed, uniqueId=%s.", uniqueId.c_str());
        /* check return value after result got */
    }
    int getRetRes = GetScriptExecResult(cmdParam, uniqueId, result);
    if (res != SUCCESS || getRetRes != SUCCESS) {
        return FAILED;
    }
    return SUCCESS;
}

int WinCmdExector::CheckCmdDelimiter(const std::string& str)
{
    for (const auto& item : ABNORMAL_INDICATOR) {
        if (std::string::npos != str.find(item.first, 0)) {
            return FAILED;
        }
    }
    return SUCCESS;
}

int WinCmdExector::ExecuteCmd(const std::vector<std::string> &cmdParams)
{
    LOGGUARD("");
    std::string combinStr = GenerateCmdStr(cmdParams);
    if (combinStr.empty()) {
        return FAILED;
    }
    INFOLOG("Execute command start, cmd: %s.", WIPE_SENSITIVE(combinStr).c_str());
    mp_int32 ret = ExecuteCmdInner(combinStr);
    if (ret != SUCCESS) {
        ERRLOG("Eexucte command failed, ret: %d, cmd: %s.", ret, WIPE_SENSITIVE(combinStr).c_str());
        return FAILED;
    }
    INFOLOG("Execute command success, cmd: %s.", WIPE_SENSITIVE(combinStr).c_str());
    return SUCCESS;
}

int WinCmdExector::ExecuteCmdInner(const std::string &combinStr, DWORD timeOut)
{
    LOGGUARD("");
    /* create pipe */
    HANDLE hStdInputRd;
    HANDLE hStdInputWr;
    int32_t ret = CreateProcPipe(hStdInputRd, hStdInputWr);
    if (ret != MP_SUCCESS) {
        ERRLOG("Create process pipe failed.");
        return ret;
    }

    /* create process */
    PROCESS_INFORMATION piProcInfo;
    STARTUPINFO siStartInfo;
    ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION));
    ZeroMemory(&siStartInfo, sizeof(STARTUPINFO));
    siStartInfo.cb = sizeof(STARTUPINFO);
    siStartInfo.hStdInput = hStdInputRd; // 子进程标准输入-管道读端
    siStartInfo.wShowWindow = SW_HIDE;
    siStartInfo.dwFlags = STARTF_USESTDHANDLES;
    std::wstring wsCmd;
    wsCmd.assign(combinStr.begin(), combinStr.end());
    if (!CreateProcessW(NULL, (LPWSTR)wsCmd.c_str(), NULL, NULL, MP_TRUE, 0, NULL, NULL, &siStartInfo,
        &piProcInfo)) {
        ERRLOG("Create child process failed, errorcode=%d.", GetLastError());
        return FAILED;
    }

    Defer _(nullptr, [&](...) {
        CloseHandle(piProcInfo.hThread);
        CloseHandle(piProcInfo.hProcess);
    });

    /* write sensitive data to pipe */
    DWORD dwWritten;
    std::string strSenInfo = ConsolidateSenInfo();
    if (!WriteFile(hStdInputWr, strSenInfo.c_str(), strlen(strSenInfo.c_str()), &dwWritten, NULL)) {
        ERRLOG("Write sensitive info to stdInput failed.");
        CleanMemorySenInfo(strSenInfo);
        return FAILED;
    }
    CleanMemorySenInfo(strSenInfo);

    if (WaitForSingleObject(piProcInfo.hProcess, timeOut) != WAIT_OBJECT_0) {
        ERRLOG("Wait for single object failed, errno=%d.", errno);
        return FAILED;
    }
    int exitCode = 0;
    if (!GetExitCodeProcess(piProcInfo.hProcess, (LPDWORD)&exitCode)) {
        ERRLOG("Could not get process exit code.");
        return FAILED;
    }
    DBGLOG("Execute script exit code is %d.", exitCode);
    return exitCode;
}

std::string WinCmdExector::GenerateCmdStr(const std::vector<std::string> &cmdParams)
{
    LOGGUARD("");
    std::string res = "PowerShell -Command ";
    std::string combinStr = "";
    for (const auto &cmd : cmdParams) {
        if (CheckCmdDelimiter(cmd) != SUCCESS) {
            ERRLOG("Check input param failed, cmd: %s.", WIPE_SENSITIVE(cmd).c_str());
            return "";
        }
        if (cmd.empty()) {
            continue;
        }
        combinStr += cmd;
        combinStr += SPACE_STR;
    }
    if (combinStr.empty()) {
        ERRLOG("Exec input param is empty.");
        return "";
    }
    return res + combinStr;
}

int WinCmdExector::DeleteResultFile(const std::string &filePath)
{
    LOGGUARD("");
    if (Module::CFile::DelFile(filePath) != SUCCESS) {
        WARNLOG("Delete file failed, path: %s.", filePath.c_str());
        return FAILED;
    }
    return SUCCESS;
}

int WinCmdExector::WriteFileWithRetry(const std::string &filePath, const std::string &content)
{
    LOGGUARD("");
    int retryCnt = 0;
    do {
        DBGLOG("Start retry write file, retry count: %d.", retryCnt + 1);
        std::vector<std::string> vecInput({content});
        std::string tempFilePath = filePath;
        if (Module::CIPCFile::WriteFile(tempFilePath, vecInput) == SUCCESS) {
            return SUCCESS;
        }
        ERRLOG("Write file failed, path: %s.", filePath.c_str());
    } while (++retryCnt < RETRY_TIME);

    return FAILED;
}

int WinCmdExector::WriteInfoToParamFile(const std::string &filePath, const Json::Value &param)
{
    LOGGUARD("");
    Json::FastWriter jsonWriter;
    std::string inputStr = jsonWriter.write(param);
    std::ofstream file(filePath, std::ios::out | std::ios::binary);
    file.write(inputStr.c_str(), inputStr.size());
    if (!file.good()) {
        WARNLOG("Write file failed, will to rewrite, path: %s.", filePath.c_str());
        if (WriteFileWithRetry(filePath, inputStr) != SUCCESS) {
            ERRLOG("Write file failed, path: %s.", filePath.c_str());
            return FAILED;
        }
    }
    if (!Module::CFile::FileExist(filePath.c_str())) {
        ERRLOG("File not exist, path: %s.", filePath.c_str());
        return FAILED;
    }
    DBGLOG("Write param file sucess, path: %s.", filePath.c_str());
    return SUCCESS;
}

int WinCmdExector::GetScriptExecResult(const Param &cmdParam, const std::string &uniqueId, Json::Value &result)
{
    LOGGUARD("");
    std::string actionResult;
    if (Module::CFile::ReadFile(GetResultFile(uniqueId), actionResult) != SUCCESS
        || actionResult.empty()) {
        ERRLOG("Read action result file failed, path: %s.", GetResultFile(uniqueId).c_str());
        return FAILED;
    }
    INFOLOG("Read result: %s", actionResult.c_str());
    if (!Module::JsonHelper::JsonStringToJsonValue(actionResult, result)) {
        ERRLOG("Failed to convert string to json actionResult:%s.", actionResult.c_str());
        return FAILED;
    }
    INFOLOG("Get result success!");
    return SUCCESS;
}

std::string WinCmdExector::ConsolidateSenInfo()
{
    std::map<std::string, std::string> sensitiveInfos;
    sensitiveInfos["UserName"] = m_param.userName;
    sensitiveInfos["Password"] = m_param.password;

    Json::Value sensitiveValue;
    for (auto &it : sensitiveInfos) {
        sensitiveValue[it.first] = std::move(it.second);
        CleanMemorySenInfo(it.second);
    }
    return Json::FastWriter().write(sensitiveValue);
}

void WinCmdExector::CleanMemorySenInfo(std::string &senInfo)
{
    // 敏感数据处理，进行3次覆写
    for (int i = 0; i < SENINFOWIPETIMES; i++) {
        senInfo.replace(0, senInfo.length(), "");
    }
}

int WinCmdExector::CreateProcPipe(HANDLE &hStdInputRd, HANDLE &hStdInputWr)
{
    SECURITY_ATTRIBUTES saAttr;
    saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
    saAttr.bInheritHandle = TRUE;
    saAttr.lpSecurityDescriptor = NULL;
    if (!CreatePipe(&hStdInputRd, &hStdInputWr, &saAttr, 0)) { // 创建匿名管道
        mp_int32 retCode = GetLastError();
        ERRLOG("CreatePipe failed, errorCode=%d", retCode);
        return retCode;
    }
    // 设置子进程不能继承接收输入管道的另一端：ChildIn_Write
    if (!SetHandleInformation(hStdInputWr, HANDLE_FLAG_INHERIT, 0)) {
        mp_int32 retCode = GetLastError();
        ERRLOG("Set handle info failed, errorCode=%d", retCode);
        return retCode;
    }
    return MP_SUCCESS;
}

std::string WinCmdExector::GetParamFile(const std::string &uuid)
{
    return std::string(VIRT_HYPERV_PARAM_PATH + "param" + uuid);
}

std::string WinCmdExector::GetResultFile(const std::string &uuid)
{
    return std::string(VIRT_HYPERV_RESULT_PATH + "result" + uuid);
}

} // namespace Utils
} // namespace HyperVPlugin

#endif